﻿using System.Collections.Generic;
using XNAFight.Engine;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using XNAFight.Characters;
using System;


namespace XNAFight.Characters.Core
{
    /// TAGS : un pour chaque état possible du personnage
    public enum CharStates 
    { 
        STAND, 
        STANDING, 
        CROUCH, 
        CROUCHING,
        UJUMP, 
        BJUMP, 
        FJUMP, 
        STUN, 
        GROUND, 
        DASH, 
        BACKDASH, 
        COUP_CROUCH, 
        COUP_STAND, 
        COUP_AIR, 
        WALKFORWARD, 
        WALKBACK, 
        STAND_GUARD, 
        CROUCH_GUARD, 
        HIT_STUN, 
        BLOCK_STUN, 
        FLIP, 
        FLIP_CROUCH 
    }
    
    // flag permettant de setter la propriété de position verticale
    public enum PositionVerticale { CROUCH, STAND, AIR }
    
    public class Character : GameObject
    {
        #region CHAMPS

        // Champs de définition du perso
        // ------------------------------------
        protected string name;
        private int vie_courante;
        private int stun_courant;
        private int vie_max;
        private int stun_max;
        private Vector2 vitesse_marche;   //en px/frame
        private Vector2 vitesse_marche_recule;
        protected Dash dash;
        protected Dash backdash;
        protected Jump ujump;
        protected Jump fjump;
        protected Jump bjump;
        
        

        //Flags
        private PositionVerticale position_verticale;
        // -------------------------------------

        
        // Champs nécessaires à la logique de la classe
        // -------------------------------------
        private BasicMovesBuffer buffer;
        private CharStates state;
        private List<BMoveSequence> sequences;
        private int nb_frames_sans_stun;
        private int max_frames_sans_stun;   // temps d'attente avant de récupérer son stun si on se prend pas de dégats
        private int decrease_stun;          // variable utilisée pour avoir un stun qui décroit de plus en plus vite, il me semble que c géré comme ça
        private bool looking_right;         // pour savoir dans quelle direction est orienté le perso
        private Vector2 position_ennemi;
        private int hauteur_sol;            // hum, p-e qu'on devrait déplacer ça non ?
        private Vector2 gravite;            // pour l'instant je le déclare ici, mais je ne suis pas sûr que ça doive rester là
        private Coup coup_prioritaire;
        private Coup coup_en_cours;
        private Vector2 vitesse;
        // -------------------------------------
                
        #endregion

        #region PROPRIETES

        public Vector2 Vitesse_marche
        {
            get { return vitesse_marche; }
            set { vitesse_marche = value; }
        }
        public Vector2 Vitesse_marche_recule
        {
            get { return vitesse_marche_recule; }
            set { vitesse_marche_recule = value; }
        }
        public string Name
        {
            get { return name; }
        }
        public int Vie_max
        {
            get
            {
                return vie_max;
            }
            set
            {
                if (value > 0)
                    vie_max = value;
            }
        }
        public int Vie_courante
        {
            get
            {
                return vie_courante;
            }
            set
            {
                vie_courante = value;
            }
        }
        public int Stun_max
        {
            get
            {
                return stun_max;
            }
            set
            {
                if (value > 0)
                    stun_max = value;
            }
        }
        public int Stun_courant
        {
            get
            {
                return stun_courant;
            }
            set
            {
                stun_courant = value;
            }

        }
        public CharStates State
        {
            get
            {
                return state;
            }
            set
            {
                state = value;
            }
        }
        public bool KO
        {
            get
            {
                return (vie_courante <= 0);
            }
        }
        public bool LookRight
        {
            get { return looking_right; }
            set { looking_right = value; }
        }
        public BasicMovesBuffer Buffer
        {
            get { return buffer; }
        }
        public List<BMoveSequence> Sequences
        {
            get { return sequences; }
        }
        public PositionVerticale PositionVerticale
        {
            get { return position_verticale; }
        }
        public int HauteurSol
        {
            get { return hauteur_sol; }
            set { hauteur_sol = value; }
        }
        public Vector2 Position_Ennemi
        {
            get { return position_ennemi; }
            set { position_ennemi = value; }
        }
        public Vector2 Vitesse
        {
            get { return vitesse; }
            set { vitesse = value; }
        }
        #endregion

        #region CONSTRUCTEUR

        // pour l'instant je vois surtout la vie, le stun et la vitesse
        // il faudra passer le sprite et la liste de coups je pense, mais pour l'instant je sais pas trop comment ça va se présenter
        public Character(ContentManager _ContentManager, Vector2 _position_depart) : base(_ContentManager)
        {
            sequences = new List<BMoveSequence>();
            buffer = new BasicMovesBuffer(30);
            name = "Unknown";
            State = CharStates.STAND;
            Stun_courant = 0;
            max_frames_sans_stun = 120;     // on pourrait le mettre en constante, je me prends pas la tête, je le mets là
            decrease_stun = 1;
            looking_right = true;
            Position = _position_depart;
            position_verticale = PositionVerticale.STAND;
            vitesse_marche = new Vector2(2.0f, 0.0f);
            vitesse_marche_recule = new Vector2(1.0f, 0.0f);
            dash = new Dash(40, 20);
            backdash = new Dash(-40, 20);
            ujump = new Jump(new Vector2(0, 20), 1.0f);
            fjump = new Jump(new Vector2(6, 20), 1.0f);
            bjump = new Jump(new Vector2(-6, 20), 1.0f);
            position_ennemi = Vector2.Zero;
            vitesse = Vector2.Zero;

            // Temporaire, je voudrai tester un systeme physique simplifié
            gravite = new Vector2(0.0f, 0.5f);

        }

        #endregion

        #region UPDATES

        public override void Update(float _elapsed)
        {
            base.Update(_elapsed);

            // update la vitesse du perso avant tout traitement (vitesse résiduelle de l'état précédent, comme le saut par exemple) :
            //  - gravité si le perso est en l'air
            //  - on stoppe les vitesses latérales aussi si on est au sol
            updateVitesse();

            updateStun();
            
            coup_prioritaire = getCoupPrioritaire();
            updateState(buffer.LastBMoves[0]);
            stateTreatment();

            
            // la position est updatée en fonction de la vitesse
            updatePosition();

            if (Sprite != null)
            {
                updateAnimation();
            }
        }

        private void updateState(List<BMove> _last_basic_moves)
        {     
            switch (state)
            {
                #region WALK BACK
                case CharStates.WALKBACK:
                    
                #endregion    
                #region WALK FORWARD
                case CharStates.WALKFORWARD:
                    
                #endregion
                #region FLIP
                case CharStates.FLIP:

                #endregion
                #region FLIP_CROUCH
                case CharStates.FLIP_CROUCH:

                #endregion
                #region STAND
                case CharStates.STAND:
                    if (coup_prioritaire != null && coup_prioritaire.Stand)
                    {
                        if (coup_prioritaire.Anime_name != "")
                        {
                            setState(CharStates.COUP_STAND);
                        }
                        break;
                    }

                    if (forwardDashOK())
                    {
                        setState(CharStates.DASH);
                        break;
                    }
                    if (backDashOK())
                    {
                        setState(CharStates.BACKDASH);
                        break;
                    }

                    if (((position_ennemi.X < position.X) && LookRight) || ((position_ennemi.X > position.X) && !LookRight))
                    {
                        setState(CharStates.FLIP);
                        break;
                    }

                    /// s'il n'y a pas d'input on reste en stand, donc pas besoin de verifier s'il n'y a pas d'input ///                    
                    if (_last_basic_moves.Count == 0)
                    {
                        setState(CharStates.STAND);
                        break;
                    }

                    foreach (BMove _BMove in _last_basic_moves)
                    {
                        if ((_BMove == BMove.UP) || (_BMove == BMove.UP_BACK) || (_BMove == BMove.UP_FORWARD))
                        {
                            if(_BMove == BMove.UP)
                            {
                                setState(CharStates.UJUMP);
                                break;
                            }
                            if(_BMove == BMove.UP_BACK)
                            {
                                setState(CharStates.BJUMP);
                                break;
                            }
                            if(_BMove == BMove.UP_FORWARD)
                            {
                                setState(CharStates.FJUMP);
                                break;
                            }
                            break;
                        }
                        if ((_BMove == BMove.DOWN) || (_BMove == BMove.DOWN_FORWARD) || (_BMove == BMove.DOWN_BACK))
                        {
                            setState(CharStates.CROUCHING);
                            break;
                        }
                        if (_BMove == BMove.FORWARD)
                        {
                            setState(CharStates.WALKFORWARD);
                            break;
                        }
                        if (_BMove == BMove.BACK)
                        {
                            setState(CharStates.WALKBACK);
                            break;
                        }
                    }

                    

                    
                    
                    break;
                #endregion
                #region CROUCH
                case CharStates.CROUCH:
                    // if (...) { setState(CharStates.COUP); }

                    if (((position_ennemi.X < position.X) && LookRight) || ((position_ennemi.X > position.X) && !LookRight))
                    {
                        setState(CharStates.FLIP_CROUCH);
                        break;
                    }

                    if (_last_basic_moves.Count == 0)
                    {
                        setState(CharStates.STANDING);
                        break;
                    }

                    foreach (BMove _BMove in _last_basic_moves)
                    {
                        if ((_BMove == BMove.DOWN) || (_BMove == BMove.DOWN_FORWARD) || (_BMove == BMove.DOWN_BACK))
                        {
                            setState(CharStates.CROUCH);
                            break;
                        }
                        else
                        {
                            setState(CharStates.STANDING);
                        }
                    }

                    
                    break;
                #endregion
                #region CROUCHING
                case CharStates.CROUCHING:
                    if (_last_basic_moves.Count == 0)
                    {
                        setState(CharStates.STANDING);
                        break;
                    }
                    foreach (BMove _BMove in _last_basic_moves)
                    {
                        if ((_BMove == BMove.DOWN) || (_BMove == BMove.DOWN_FORWARD) || (_BMove == BMove.DOWN_BACK))
                        {
                            if (Sprite.FinAnim)
                                setState(CharStates.CROUCH);
                            else
                                setState(CharStates.CROUCHING);
                            break;
                        }
                        else
                        {
                            setState(CharStates.STANDING);
                        }
                    }
                    
                    break;
                #endregion
                #region STANDING
                case CharStates.STANDING:
                    
                    if (forwardDashOK())
                    {
                        setState(CharStates.DASH);
                        break;
                    }
                    if (forwardDashOK())
                    {
                        setState(CharStates.BACKDASH);
                        break;
                    }

                    if (_last_basic_moves.Count == 0)
                    {
                        if (Sprite.FinAnim)
                            setState(CharStates.STAND);
                        else
                            setState(CharStates.STANDING);
                        break;
                    }
                    foreach (BMove _BMove in _last_basic_moves)
                    {
                        if ((_BMove == BMove.DOWN) || (_BMove == BMove.DOWN_FORWARD) || (_BMove == BMove.DOWN_BACK))
                        {
                            setState(CharStates.CROUCHING);
                            break;
                        }
                        else
                        {
                            if (Sprite.FinAnim)
                                setState(CharStates.STAND);
                            else
                                setState(CharStates.STANDING);
                        }
                    }                    
                    break;
                #endregion
                #region DASH
                case CharStates.DASH:
                    break;
                #endregion
                #region BACK DASH
                case CharStates.BACKDASH:
                    break;
                #endregion
                #region UJUMP
                case CharStates.UJUMP :
                    break;
                #endregion
                #region FJUMP
                case CharStates.FJUMP:
                    break;
                #endregion
                #region BJUMP
                case CharStates.BJUMP:
                    break;
                #endregion
                #region COUP_STAND
                case CharStates.COUP_STAND:
                    break;
                #endregion
                #region COUP_CROUCH
                case CharStates.COUP_CROUCH:
                    break;
                #endregion
                #region COUP_AIR
                case CharStates.COUP_AIR:
                    break;
                #endregion
                default :
                    setState(CharStates.STAND);
                    break;                    
            }            
        }

        private void stateTreatment()
        {
            // to do

            // je commence avec le traitement des états de move
            switch (State)
            {
                case CharStates.WALKFORWARD:
                    marche(true);
                    break;
                case CharStates.WALKBACK:
                    marche(false);
                    break;
                case CharStates.DASH:
                    dashe();
                    break;
                case CharStates.BACKDASH:
                    backDashe();
                    break;
                case CharStates.UJUMP:
                    sauter_droit();
                    break;
                case CharStates.FJUMP:
                    sauter_avant();
                    break;
                case CharStates.BJUMP:
                    sauter_arriere();
                    break;
                case CharStates.COUP_STAND:
                    coup_en_cours.update();
                    position_verticale = coup_en_cours.getPositionverticale();
                    break;
                default :
                    break;
            }
        }

        private void updateStun()
        {
            if (State != CharStates.STUN && State != CharStates.HIT_STUN)
            {
                nb_frames_sans_stun++;
            }

            if (nb_frames_sans_stun >= max_frames_sans_stun)
            {
                stun_courant -= decrease_stun;
                decrease_stun++;
            }
        }

        public virtual void updateAnimation()
        {
            if (LookRight)
                Sprite.Sprite_effect = SpriteEffects.FlipHorizontally;
            else
                Sprite.Sprite_effect = SpriteEffects.None;            
        }

        
        private void updatePosition()
        {
            Position += Vitesse;
        }

        private void updateVitesse()
        {
            Vitesse += gravite;
            if (Position.Y >= HauteurSol)
            {
                Vitesse = new Vector2(0.0f, 0.0f);
            }
        }

        #endregion
       
        #region AUTRE METHODES

        private void setState(CharStates _state)
        {
            state = _state;
            switch (state)
            {
                case CharStates.CROUCHING: setFlag(PositionVerticale.CROUCH);
                    break;
                case CharStates.CROUCH: setFlag(PositionVerticale.CROUCH);
                    break;
                case CharStates.STANDING: setFlag(PositionVerticale.STAND);
                    break;
                case CharStates.WALKBACK:
                case CharStates.WALKFORWARD:
                case CharStates.STAND: setFlag(PositionVerticale.STAND);
                    break;
                case CharStates.DASH: dash.init(looking_right);
                    break;
                case CharStates.BACKDASH: backdash.init(looking_right);
                    break;
                case CharStates.UJUMP: ujump.init(looking_right);
                    break;
                case CharStates.FJUMP: fjump.init(looking_right);
                    break;
                case CharStates.BJUMP: bjump.init(looking_right);
                    break;
                case CharStates.FLIP: LookRight = !LookRight;
                    break;
                case CharStates.FLIP_CROUCH: LookRight = !LookRight;
                    break;
                case CharStates.COUP_AIR:
                case CharStates.COUP_CROUCH:
                case CharStates.COUP_STAND: coup_en_cours = coup_prioritaire;
                    break;
            }
        }
        private void setFlag(PositionVerticale _flag)
        {
            position_verticale = _flag;
        }

        private void increaseStun(int stun)
        {
            stun_courant += stun;
            nb_frames_sans_stun = 0;
            decrease_stun = 1;

            if (stun_courant >= stun_max)
            {
                state = CharStates.STUN;
                stun_max = (int)(stun_max * 1.25);
                stun_courant = 0;
            }
        }

        public override void Load()
        {
            base.Load();
        }

        public Coup getCoupPrioritaire()
        {
            List<BMoveSequence> moves_buffer = MovesInterpreter.getMovesFromBuffer(sequences, buffer.LastBMoves);
            List<BMoveSequence> moves_flags = MovesInterpreter.getMovesFromFlags(moves_buffer, position_verticale);
            BMoveSequence priority_move = MovesInterpreter.getPriorityMove(moves_flags);
            Coup coup_prioritaire = null;
            if (priority_move != null)
                coup_prioritaire = priority_move.Coup;

            return coup_prioritaire;
        }

        public bool forwardDashOK()
        {
            return MovesInterpreter.isDashingForward(buffer.LastBMoves);
        }
        public bool backDashOK()
        {
            return MovesInterpreter.isDashingBackward(buffer.LastBMoves);
        }
        
        public void backDashe()
        {
            bool last_frame = !backdash.update();
            Position += backdash.Translation;

            if (last_frame)
                setState(CharStates.STAND);
        }
        public void dashe()
        {
            bool last_frame = !dash.update();
            Position += dash.Translation;

            if (last_frame)
                setState(CharStates.STAND);
        }
        public void sauter_droit()
        {
            ujump.update();
            Position += ujump.Translation;

            if (position.Y >= hauteur_sol)
            {
                Position = new Vector2(Position.X, hauteur_sol); ;
                setState(CharStates.STAND);
            }
        }
        public void sauter_avant()
        {
            fjump.update();
            Position += fjump.Translation;

            if (position.Y >= hauteur_sol)
            {
                Position = new Vector2(Position.X, hauteur_sol); ;
                setState(CharStates.STAND);
            }
        }
        public void sauter_arriere()
        {
            bjump.update();
            Position += bjump.Translation;

            if (position.Y >= hauteur_sol)
            {
                Position = new Vector2(Position.X, hauteur_sol); ;
                setState(CharStates.STAND);
            }
        }

        public void marche(bool forward)
        {
            
            /*if ((forward && LookRight) || (!forward && !looking_right))
            {
                if (forward)
                    Position += new Vector2(Vitesse_marche, 0.0f);
                else
                    Position += new Vector2(Vitesse_marche_recule, 0.0f);
            }
            else
            {
                if(forward)
                    Position -= new Vector2(Vitesse_marche, 0.0f);
                else
                    Position -= new Vector2(Vitesse_marche_recule, 0.0f);
            }*/

            if ((forward && LookRight) || (!forward && !looking_right))
            {
                if (forward)
                    Vitesse += Vitesse_marche;
                else
                    Vitesse += Vitesse_marche_recule;
                    
            }
            else
            {
                if (forward)
                    Vitesse -= Vitesse_marche;
                else
                    Vitesse -= Vitesse_marche_recule;
                    
            }
        }

        #endregion
    }
}
